﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml.Linq;
using NeonMika.EightTracks.Responses;
using System.ComponentModel;
using System.Threading;
using System.Diagnostics;

namespace NeonMika.EightTracks
{
    public class EightTracksConnection : INotifyPropertyChanged
    {
        // Declare the event
        public event PropertyChangedEventHandler PropertyChanged;

        string rootPath = EightTracks.Properties.Settings.Default.RootPath;
        private string play_token;
        string api_key;

        private AuthentificationResponse _authentification;
        public AuthentificationResponse Authentification
        {
            get
            {
                return _authentification;
            }

            private set
            {
                _authentification = value;
                OnPropertyChanged("Authentification");
                OnPropertyChanged("LoggedIn");
            }
        }

        public bool LoggedIn
        {
            get
            {
                if (Authentification != null && Authentification.LoggedIn == true)
                    return true;
                else
                    return false;
            }
        }

        /// <summary>
        /// Creates a connection to the 8tracks API
        /// </summary>
        /// <param name="developerKey">Request a developer key at the API section on 8tracks.com</param>
        public EightTracksConnection(string developerKey)
        {
            rootPath = "http://8tracks.com";
            this.api_key = developerKey;
        }

        /// <summary>
        /// Uses the POST authentification method

        /// </summary>
        /// <param name="name">Username</param>
        /// <param name="password">Password</param>
        /// <returns>True on successful log-in, otherwise false</returns>
        public bool Authenticate(string name, string password)
        {
            //Set URL
            string uri = rootPath + "/sessions.xml?api_key=" + api_key;
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
            //Uses POST
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            //Write in request body
            Stream stream = request.GetRequestStream();
            string data = "login=" + name + "&password=" + password;
            stream.Write(Encoding.UTF8.GetBytes(data), 0, data.Length);
            stream.Close();
            try
            {
                //Get response, on error jump to catch clause
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                XDocument reader = XDocument.Load(response.GetResponseStream());
                XElement root = reader.Root;
                //On success, set Authentification property
                Authentification = new AuthentificationResponse(root);
                return LoggedIn;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Combines api_key and user_token to one return-string
        /// Starts and ends WITHOUT &
        /// </summary>
        /// <returns>
        /// api_key=xxx[&user_token=xxx]
        /// </returns>
        private string CreateApiKeyAndAuthentificationString()
        {
            string uri = "api_version=2&api_key=" + api_key;
            if (Authentification != null && Authentification.LoggedIn == true)
                uri += "&user_token=" + Authentification.UserToken.Replace(",", "%3B");
            return uri;
        }

        /// <summary>
        /// Returns a MixesResponse which contains a List of mixes
        /// </summary>
        /// <param name="criterias">Name of mix or singer/band</param>
        /// <param name="tags">Genres</param>
        /// <param name="perpage">Mixes per page</param>
        /// <param name="sort">EightTracks.Sort parameter</param>
        /// <param name="page">Which page to be loaded (starting with 1)</param>
        /// <returns>MixResponse on connection and success, otherwise null</returns>
        public MixesResponse GetMixes(string[] criterias, string[] tags, int? perpage, Sort sort, int? page)
        {
            string uri = rootPath + "/mixes.xml?" + CreateApiKeyAndAuthentificationString();

            if (criterias != null && criterias.Length > 0)
            {
                uri += "&q=";
                for (int i = 0; i < criterias.Length; i++)
                {
                    uri += criterias[i];
                    if (i + 1 != criterias.Length)
                        uri += "%2B";
                }
            }

            if (tags != null && tags.Length > 0)
            {
                uri += "&tags=";
                for (int i = 0; i < tags.Length; i++)
                {
                    uri += tags[i].Replace(" ", "+");
                    if (i + 1 != tags.Length)
                        uri += "%2B";
                }
            }

            if (perpage != null && perpage > 0)
                uri += "&per_page=" + perpage;

            if (sort != Sort.Random)
            {
                uri += "&sort=";
                switch (sort)
                {
                    case Sort.Hot:
                        uri += "hot";
                        break;
                    case Sort.Popular:
                        uri += "popular";
                        break;
                    case Sort.Recent:
                        uri += "recent";
                        break;
                }
            }

            if (page != null && page > 0)
                uri += "&page=" + page;

            Stream s = GetWebResponseStream(uri);
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new MixesResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Get a mixes comments
        /// </summary>
        /// <param name="mixId">From which mix the comments should be loaded</param>
        /// <param name="perPage">How many per page</param>
        /// <param name="page">Which page</param>
        /// <returns>ReviewsResponse with a list of Revies or null if no connection could be established</returns>
        public ReviewsResponse GetReviewsByMixId(int mixId, int perPage, int page)
        {
            Stream s = GetWebResponseStream(rootPath + "/mixes/" + mixId + "/reviews.xml?per_page?=" + perPage + "&page=" + page + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new ReviewsResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Get reviews written by a certain user
        /// </summary>
        /// <param name="userId">userId which has written the reviews</param>
        /// <param name="perPage">How many reviews should be loaded per page</param>
        /// <param name="page">Which page</param>
        /// <returns>ReviewsResponse with list of Reviews or null if no connection could be established</returns>
        public ReviewsResponse GetReviewsByUserId(int userId, int perPage, int page)
        {
            Stream s = GetWebResponseStream(rootPath + "/users/" + userId + "/reviews.xml?per_page=" + perPage + "&page=" + page + "&"+ CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new ReviewsResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Post a review on a mix
        /// </summary>
        /// <param name="mixId">mixId on which the message should be posted</param>
        /// <param name="message">Review message</param>
        /// <returns>The written Review or null if no connection could be established</returns>
        public ReviewResponse PostReview(int mixId, string message)
        {
            //Set URL
            string uri = rootPath + "/reviews.xml?" + CreateApiKeyAndAuthentificationString();
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
            //Uses POST
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            //Write in request body
            Stream stream = request.GetRequestStream();
            string data = "review[mix_id]=" + mixId + "&review[body]=" + message;
            stream.Write(Encoding.UTF8.GetBytes(data), 0, data.Length);
            stream.Close();
            try
            {
                //Get response, on error jump to catch clause
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                XDocument reader = XDocument.Load(response.GetResponseStream());
                XElement root = reader.Root;
                //On success, set Authentification property
                return new ReviewResponse(root);
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// Like a Mix
        /// User has to be logged in
        /// </summary>
        /// <param name="userId">mixId which should be liked</param>
        /// <returns>MixResponse with Mix (contains "liked by user" field) or null if no connection could be established</returns>
        public MixResponse LikeMix(int mixId)
        {
            Stream s = GetWebResponseStream(rootPath + "/mixes/" + mixId + "/like.xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new MixResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Unlike a mix
        /// User has to be logged in
        /// </summary>
        /// <param name="userId">mixId which should be unliked</param>
        /// <returns>MixResponse with Mix (contains "liked by user" field) or null if no connection could be established</returns>
        public MixResponse UnlikeMix(int mixId)
        {
            Stream s = GetWebResponseStream(rootPath + "/mixes/" + mixId + "/unlike.xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new MixResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Switch from unliked to liked or vis-a-vis
        /// User has to be logged in
        /// </summary>
        /// <param name="userId">mixId which should be checked</param>
        /// <returns>MixResponse with Mix (contains "liked by user" field) or null if no connection could be established</returns>
        public MixResponse ToggleMixLike(int mixId)
        {
            Stream s = GetWebResponseStream(rootPath + "/mixes/" + mixId + "/toggle_like.xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new MixResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Get a list of tags with a link to the next page
        /// </summary>
        /// <param name="perpage">How many tags should be retourned per page</param>
        /// <param name="page">Which page should be loaded</param>
        /// <returns>TagsResponse with a list of Tags or null if no connection could be established</returns>
        public TagsResponse GetAllTags(int perpage, int page)
        {
            if (perpage <= 0 || page <= 0)
                return null;

            Stream s = GetWebResponseStream(rootPath + "/tags.xml?per_page=" + perpage + "&page=" + page + "&" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new TagsResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Get a User
        /// </summary>
        /// <param name="userId">userId</param>
        /// <returns>UserResponse containing a user or null if no connection could be established</returns>
        public UserResponse GetUser(int userId)
        {
            Stream s = GetWebResponseStream(rootPath + "/users/" + userId + ".xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                StreamReader sr = new StreamReader(s);
                XDocument xmlDoc = XDocument.Load(s);
                return new UserResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Get a User
        /// </summary>
        /// <param name="userName">Login name</param>
        /// <returns>UserResponse containing a User or null if no connection could be established</returns>
        public UserResponse GetUser(string userName)
        {
            //AbsoluteUri = "http://8tracks.com/users/markus.weninger.xml?api_version=2&api_key=47310297ad25a2e19ef55dfb5f8c088dd4af0617&user_token=1559559;0d283fc78028d501841f61bdfe401f4dfafaa5a2"

            Stream s = GetWebResponseStream(rootPath + "/users/" + userName + ".xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new UserResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Searches for Tags containing the keyword
        /// Perfect for auto-complete
        /// </summary>
        /// <param name="key">Word (or part of word) which should be contained in the Tag</param>
        /// <returns>TagsResponse with list of Tags or null if no connection could be established</returns>
        public TagsResponse GetTagsByKeyword(string key)
        {
            Stream s = GetWebResponseStream(rootPath + "/tags.xml?q=" + key + "&" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new TagsResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Like a track
        /// User has to be logged in
        /// </summary>
        /// <param name="userId">trackId which should be liked</param>
        /// <returns>TrackResponse with Track (contains "liked by user" field) or null if no connection could be established</returns>
        public TrackResponse FavouriteTrack(int trackId)
        {
            Stream s = GetWebResponseStream(rootPath + "/tracks/" + trackId + "/fav.xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new TrackResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Unlike a track
        /// User has to be logged in
        /// </summary>
        /// <param name="userId">trackId which should be unliked</param>
        /// <returns>TrackResponse with Track (contains "liked by user" field) or null if no connection could be established</returns>
        public TrackResponse UnfavouriteTrack(int trackId)
        {
            Stream s = GetWebResponseStream(rootPath + "/tracks/" + trackId + "/unfav.xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new TrackResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Check if a track is liked
        /// User has to be logged in
        /// </summary>
        /// <param name="userId">trackId which should be checked</param>
        /// <returns>TrackResponse with Track (contains "liked by user" field) or null if no connection could be established</returns>
        public TrackResponse ToggleTrackFavourite(int trackId)
        {
            Stream s = GetWebResponseStream(rootPath + "/tracks/" + trackId + "/toggle_fav.xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new TrackResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Follow a DJ
        /// User has to be logged in
        /// </summary>
        /// <param name="userId">userId which should be followed</param>
        /// <returns>UserResponse with User (contains "followed by user" field) or null if no connection could be established</returns>
        public UserResponse FollowUser(int userId)
        {
            Stream s = GetWebResponseStream(rootPath + "/users/" + userId + "/follow.xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new UserResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Unfollow a DJ
        /// User has to be logged in
        /// </summary>
        /// <param name="userId">userId which should be unfollowed</param>
        /// <returns>UserResponse with User (contains "followed by user" field) or null if no connection could be established</returns>
        public UserResponse UnfollowUser(int userId)
        {
            Stream s = GetWebResponseStream(rootPath + "/users/" + userId + "/unfollow.xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new UserResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Check the follow status
        /// User has to be logged in
        /// </summary>
        /// <param name="userId">userId which should be checked if followed</param>
        /// <returns>UserResponse with User (contains "followed by user" field) or null if no connection could be established</returns>
        public UserResponse ToggleUserFollow(int userId)
        {
            Stream s = GetWebResponseStream(rootPath + "/users/" + userId + "/toggle_follow.xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new UserResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Get a mix by it's id
        /// </summary>
        /// <param name="id">The mix' id</param>
        /// <returns>Mix on success, otherwise null</returns>
        public MixResponse GetMix(int id)
        {
            Stream s = GetWebResponseStream(rootPath + "/mixes/" + id + ".xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new MixResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// If no play token is present on the running system request a new one from 8tracks with this method
        /// You can use a play_token as long as you want.
        /// To get the best out of it, use one play_token per user
        /// </summary>
        /// <returns>PlayTokenResponse which contains a new play_token or null if no connection could be established</returns>
        private PlayTokenResponse RequestNewPlayToken()
        {
            Stream s = GetWebResponseStream(rootPath + "/sets/new.xml?" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new PlayTokenResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// After requesting a new play_token it's best to save your play_token to the file system and load it on the next start
        /// </summary>
        /// <param name="token">The token to be saved</param>
        /// <returns>True on successful save, otherwise false</returns>
        private bool SavePlayTokenToFile(string token)
        {
            FileStream fs;
            StreamWriter sw;
            try
            {
                string path = EightTracks.Properties.Settings.Default.PlayTokenPath;
                if (LoggedIn)
                    path += "_" + Authentification.CurrentUser.Login + ".txt";

                fs = new FileStream(path, FileMode.Create, FileAccess.Write);
                sw = new StreamWriter(fs);
                sw.WriteLine(token);
                sw.Flush();
                sw.Close();
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /// <summary>
        /// Load play_token from file system
        /// </summary>
        /// <returns>Play token string if successful, otherwise null</returns>
        private string LoadPlayTokenFromFile()
        {
            FileStream fs;
            StreamReader sr;
            try
            {
                string path = EightTracks.Properties.Settings.Default.PlayTokenPath;
                if (LoggedIn)
                    path += "_" + Authentification.CurrentUser.Login + ".txt";

                fs = new FileStream(path, FileMode.Open, FileAccess.Read);
                sr = new StreamReader(fs);
                string tok = sr.ReadLine();
                sr.Close();
                return tok;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        /// <summary>
        /// Use this method to get the play_token
        /// If play_token is not set, it searches on the file system if a play_token exists, otherwise it requests a new one
        /// </summary>
        /// <returns>Play token string</returns>
        private string GetPlayToken()
        {
            if (play_token == null || play_token == "")
            {
                string playTokenFromFile = LoadPlayTokenFromFile();
                if (playTokenFromFile == null)
                {
                    play_token = RequestNewPlayToken().PlayToken;
                    SavePlayTokenToFile(play_token);
                }
                else
                    play_token = playTokenFromFile;
            }

            return play_token;
        }

        /// <summary>
        /// Get a set by it's mix' id
        /// </summary>
        /// <param name="mixId">The mix' id</param>
        /// <returns>Set or null if no connection could be established</returns>
        public SetResponse GetSet(int mixId)
        {
            Stream s = GetWebResponseStream(rootPath + "/sets/" + GetPlayToken() + "/play.xml?mix_id=" + mixId + "&" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new SetResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Call this to get the next track in the mix
        /// Watch out for Set.AtEnd -> If Set.AtEnd is true, no playable track is existent and the mix is finished
        /// </summary>
        /// <param name="mixId">Mix id in which mix the next song should be loaded</param>
        /// <returns>Set or null if no connection could be established</returns>
        public SetResponse GetNextTrackInSet(int mixId)
        {
            Stream s = GetWebResponseStream(rootPath + "/sets/" + GetPlayToken() + "/next.xml?mix_id=" + mixId + "&" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new SetResponse(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// After playing a song for 30 seconds you have to report this to 8tracks
        /// </summary>
        /// <param name="trackId">Which track was played?</param>
        /// <param name="mixId">In which mix?</param>
        /// <returns>Standard response on connection, otherwise null</returns>
        public Response Report30SecondsPlay(int trackId, int mixId)
        {
            Stream s = GetWebResponseStream("http://8tracks.com/sets/874076615/report.xml?track_id=" + trackId + "&mix_id=" + mixId + "&" + CreateApiKeyAndAuthentificationString());
            if (s != null)
            {
                XDocument xmlDoc = XDocument.Load(s);
                return new Response(xmlDoc.Root);
            }
            else
                return null;
        }

        /// <summary>
        /// Returns a response stream of a GET request to a specific URL
        /// Has a standard timeout of 10 secondes for each reqeust and repeats 5 times
        /// </summary>
        /// <param name="url">The url from which the response stream should be returned</param>
        /// <returns>Response stream on success, otherwise null</returns>
        private Stream GetWebResponseStream(string url, int timeout = 10000, int tries = 5)
        {
            if (timeout <= 0 || tries <= 0)
                return null;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Timeout = timeout;
            for (int i = 0; i < tries; i++)
            {
                try
                {
                    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                    request.Timeout = 10000;
                    return response.GetResponseStream();
                }
                catch (Exception ex) { Debug.Print(ex.ToString()); }
                Thread.Sleep(1);
            }
            return null;
        }

        // Create the OnPropertyChanged method to raise the event
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}
